#include "Debug.h"

#ifdef _DEBUG

#include <cstdarg>
#include <iostream>
#include <fstream>
#include <sstream>
#include <ctime>

#include <cv.h>
#include <highgui.h>

using namespace std;

uchar* Debug::loadImage(const char *filename, int &width, int &height) {
    IplImage *img = cvLoadImage(filename, CV_LOAD_IMAGE_GRAYSCALE);
    if(img == NULL) {
        cout << "cannot find file: " << filename << endl;
        return NULL;
    }

    uchar *imageData = (uchar *)(img->imageData);
    width = img->width;
    height = img->height;
    int widthStep = img->widthStep;

    uchar *greyData = new uchar[width*height];
    for(int i = 0; i < height; i++) {
        memcpy(greyData + width*i, imageData + widthStep*i, width*sizeof(uchar));
    }

    cvReleaseImage(&img);
    
    return greyData;
}

void Debug::showImage(const IplImage *image, int wait) {
    char winName[] = "_window";
    cvNamedWindow(winName);

    cvSetMouseCallback(winName, mouseCallback);

    cvShowImage(winName, image);

    char key;
    do {
        key = cvWaitKey(wait);

        if (key == 's') {
            stringstream ss;
            ss << time(NULL)%1000000;
            ss << ".bmp";
            
            cvSaveImage(ss.str().c_str(), image);
        }
    } while (key == 's');

    cvDestroyWindow(winName);
}

/**
 * This is a function illustrating how to show more than one image in a single window using Intel OpenCV
 *
 * @param char *title: Title of the window to be showed
 * @param int nArgs:   Number of images to be showed
 * @param ...:         IplImage*, which contains the images
 *
 * The method used is to set the ROIs of a Single Big image and then resizing 
 * and copying the input images on to the Single Big Image. This function does not 
 * stretch the image, and it resizes the image without modifying the width/height ratio..
 * 
 * This function can be called like this:
 * showManyImages("Images", 2, img1, img2);
 * or
 * showManyImages("Images", 5, img2, img2, img3, img4, img5);
 *
 * This function can show upto 12 images in a single window. It does not check whether 
 * the arguments are of type IplImage* or not. The maximum window size is 700 by 660 pixels.
 * Does not show anything if the number of arguments is less than one or greater than 12.
 * 
 * Take care of the number of arguments you pass, and the type of arguments, which should be 
 * of type IplImage* ONLY. If you pass a pointer that is not IplImage*, Error will occur.
 */

void Debug::showManyImage(char *title, int nArgs, ...) {

    // img - Used for getting the arguments 
    IplImage *img;

    // DispImage - the image in which input images are to be copied
    IplImage *DispImage;

    int size;
    int i;
    int m, n;
    int x, y;

    // w - Maximum number of images in a row 
    // h - Maximum number of images in a column 
    int w, h;

    // scale - How much we have to resize the image
    float scale;
    int max;

    // If the number of arguments is lesser than 0 or greater than 12
    // return without showing 
    if (nArgs <= 0) {
        printf("Number of arguments too small....\n");
        return;
    } else if (nArgs > 12) {
        printf("Number of arguments too large....\n");
        return;
    }        // Determine the size of the image, 
        // and the number of rows/cols 
        // from number of arguments 
    else if (nArgs == 1) {
        w = h = 1;
        size = 300;
    } else if (nArgs == 2) {
        w = 2;
        h = 1;
        size = 300;
    } else if (nArgs == 3 || nArgs == 4) {
        w = 2;
        h = 2;
        size = 300;
    } else if (nArgs == 5 || nArgs == 6) {
        w = 3;
        h = 2;
        size = 200;
    } else if (nArgs == 7 || nArgs == 8) {
        w = 4;
        h = 2;
        size = 200;
    } else {
        w = 4;
        h = 3;
        size = 150;
    }

    // Create a new 3 channel image
    DispImage = cvCreateImage(cvSize(100 + size*w, 60 + size * h), 8, 3);

    // Used to get the arguments passed
    va_list args;
    va_start(args, nArgs);

    // Loop for nArgs number of arguments
    for (i = 0, m = 20, n = 20; i < nArgs; i++, m += (20 + size)) {

        // Get the Pointer to the IplImage
        img = va_arg(args, IplImage*);

        // Check whether it is NULL or not
        // If it is NULL, release the image, and return
        if (img == 0) {
            printf("Invalid arguments");
            cvReleaseImage(&DispImage);
            return;
        }

        // Find the width and height of the image
        x = img->width;
        y = img->height;

        // Find whether height or width is greater in order to resize the image
        max = (x > y) ? x : y;

        // Find the scaling factor to resize the image
        scale = (float) ((float) max / size);

        // Used to Align the images
        if (i % w == 0 && m != 20) {
            m = 20;
            n += 20 + size;
        }

        // Set the image ROI to show the current image
        cvSetImageROI(DispImage, cvRect(m, n, (int) (x / scale), (int) (y / scale)));

        // Resize the input image and copy the it to the Single Big Image
        cvResize(img, DispImage);

        // Reset the ROI in order to show the next image
        cvResetImageROI(DispImage);
    }

    // Create a new window, and show the Single Big Image
    cvNamedWindow(title, 1);
    cvShowImage(title, DispImage);

    cvWaitKey();
    cvDestroyWindow(title);

    // End the number of arguments
    va_end(args);

    // Release the Image Memory
    cvReleaseImage(&DispImage);
}

void Debug::showImageData(const uchar *iData, int iWidth, int x, int y, int pW, int pH, int wait) {
    IplImage *image = cvCreateImage(cvSize(pW, pH), 8, 1);

    int widthStep = image->widthStep;
    for (int i = 0; i < pH; i++) {
        memcpy(image->imageData + widthStep*i, iData + iWidth * (y + i) + x, pW*sizeof(uchar));
    }

    showImage(image, wait);
    cvReleaseImage(&image);
}

void Debug::imageData2bmp(const uchar *imageData, int width, int height, const char *filename) {
    IplImage *image = cvCreateImage(cvSize(width, height), 8, 1);

    int widthStep = image->widthStep;
    for (int i = 0; i < height; i++) {
        memcpy(image->imageData + widthStep*i, imageData + width*i, width*sizeof(uchar));
    }

    cvSaveImage(filename, image);
    cvReleaseImage(&image);
}

bool Debug::isBinarized(const uchar *imageData, int width, int height) {
    bool isBinarized = true;

    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            if (*(imageData + width * i + j) != 0 && *(imageData + width * i + j) != 255) {
                std::cout << "(" << j << ", " << i << ") = " << (int) (uchar)*(imageData + width * i + j) << " not binarized\n";

                isBinarized = false;
            }
        }
    }

    return isBinarized;
}

bool Debug::isBinarized(const IplImage *image) {
    int width = image->width, height = image->height;
    uchar *data = (uchar *) image->imageData;

    bool isBinarized = true;
    for (int i = 0; i < height; i++) {
        data = (uchar *)(image->imageData + i * image->widthStep);

        for (int j = 0; j < width; j++) {
            if (*(data + j) != 0 && *(data + j) != 255) {
                std::cout << "(" << j << ", " << i << ") = " << (int) (uchar)*(data + j) << " is not binarized\n";

                isBinarized = false;
            }
        }
    }

    return isBinarized;
}

void Debug::binarize(uchar *imageData, long size, uchar threshold) {
    for (int i = 0; i < size; i++) {
        if (*(imageData + i) < threshold) {
            *(imageData + i) = 0;
        }else {
            *(imageData + i) = 255;
        }
    }
}

void Debug::binarize(IplImage *image, uchar threshold) {
    int width = image->width, height = image->height;
    uchar *data;

    for (int i = 0; i < height; i++) {
        data = (uchar *) (image->imageData + i * image->widthStep);

        for (int j = 0; j < width; j++) {
            if (*(data + j) < threshold) {
                *(data + j) = 0;
            }else {
                *(data + j) = 255;
            }
        }
    }
}

uchar * Debug::readImageData(const char *filename, int &width, int &height, FileType type) {
    uchar *imageData = NULL;
    
    if(type == TEXT) {
        ifstream fin(filename);
        assert(fin);

        fin >> width >> height;

        imageData = new uchar[width * height];

        int temp;
        int size = width * height;
        for (int i = 0; i < size; i++) {
            fin >> temp;
            imageData[i] = (uchar) temp;
        }

        fin.close();
    } else if (type == BINARY) {
        ifstream fin(filename, ios::binary);
        assert(fin);

        fin.read((char *) & width, sizeof (int));
        fin.read((char *) & height, sizeof (int));

        imageData = new uchar[width * height];

        for (int i = 0; i < height; i++) {
            fin.read((char *) (imageData + width*i), width*sizeof (uchar));
        }

        fin.close();
    } else {
        assert(false);
    }

    return imageData;
}

void Debug::writeImageData(const char *filename, uchar *imageData, int width, int height, FileType type) {
    if(type == TEXT) {
        ofstream fout(filename);

        fout << width << " " << height << endl;
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                fout << (int)*(imageData + i * width + j) << "\t";
            }
            fout << endl;
        }

        fout.close();
    } else if (type == BINARY) {
        ofstream fout(filename, ios::binary);

        fout.write((char *)&width, sizeof(int));
        fout.write((char *)&height, sizeof(int));
        
        for (int i = 0; i < height; i++) {
            fout.write((char *)(imageData + width*i), width*sizeof(uchar));
        }

        fout.close();
    } else {
        assert(false);
    }
}

void Debug::showNormHistogram(const float *fdata, int len, int zoom, int wait) {
    if(zoom > 1) {
        int size = len * zoom;
        float *newData = new float[size]; 

        for(int i = 0; i < size; i++) {
            newData[i] = fdata[i/zoom]/zoom;
        }

        showNormHistogram(newData, size, 1, wait);

        delete []newData;
    } else if(zoom < -1) {
        int scale = 0-zoom;
        int size = (len + scale - 1)/scale;
        float *newData = new float[size];
        memset(newData, 0, size*sizeof(float));

        for(int i = 0; i < size; i++) {
            for(int j = 0; j < scale && scale*i + j < len; j++) {
                newData[i] += fdata[scale*i + j];
            }
        }

        showNormHistogram(newData, size, 1, wait);

        delete []newData;
    } else {
        int height = len * 2 / 3;

        IplImage *image = cvCreateImage(cvSize(len, height), IPL_DEPTH_8U, 1);
        uchar *imageData = (uchar *) image->imageData;

        float max = 0;
        float *times = new float[len];
        for (int i = 0; i < len; i++) {
            times[i] = fdata[i] * height;
            max = (max > times[i]) ? max : times[i];
        }

        float count = height * 4 / (5 * max);
        int widthStep = image->widthStep;
        for (int i = 0; i < len; i++) {
            int j = 0;
            int temp = cvRound(times[i] * count);
            for (; j < temp; j++) {
                *(imageData + (height - j - 1) * widthStep + i) = 0;
            }
            for (; j < height; j++) {
                *(imageData + (height - j - 1) * widthStep + i) = 255;
            }
        }

        showImage(image, wait);

        delete[] times;
        cvReleaseImage(&image);
    }
}

void Debug::printImageData(const uchar *data, int width, int height) {
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            cout.width(4);
            cout << (int) (uchar)*(data + width * i + j);
        }

        cout << endl;
    }
}

void Debug::mouseCallback(int event, int x, int y, int flags, void *param) {
    if (event & CV_EVENT_LBUTTONUP) {
        std::cout << "x: " << x << "\ty: " << y << std::endl;
    }
}

#endif
